﻿using System;
using NMock2.Syntax;

namespace NMock2
{
	/// <summary>
	/// This class represents a mock object of an interface or class.  To create a <see cref="Mock{T}"/>, 
	/// use a <see cref="MockFactory"/>.
	/// <para><span style="color:red;font-weight:bold;">This class is new to NMock3.</span></para>
	/// </summary>
	/// <typeparam name="T">The type to mock.</typeparam>
	/// <example>
	/// <code>
	/// 
	/// public class TestClass
	/// {
	///		public void TestMethod()
	///		{
	///			MockFactory factory = new MockFactory();
	///			Mock&lt;ISample&gt; mock = factory.CreateMock&lt;ISample&gt;();
	/// 
	///			//create an expectation that the property Prop will be set to 3
	///			mock.Expects.One.SetPropertyTo(m=>m.Prop=3);
	/// 
	///			new Driver(mock.MockObject).Drive(3);
	///		}
	/// }
	/// 
	/// public interface ISample
	/// {
	///		int Prop {get; set;}
	/// }
	/// 
	/// public class Driver
	/// {
	///		public Driver(ISample sample)
	///		{
	///			Sample = sample;
	///		}
	///		public int Sample {get;set;}
	///		public void Drive(int value)
	///		{
	///			Sample.Prop = value;
	///		}
	/// }
	/// 
	/// </code>
	/// </example>
	public class Mock<T> where T : class
	{
		private readonly object _proxy;

		/// <summary>
		/// 
		/// </summary>
		/// <param name="proxy"></param>
		/// <exception cref="ArgumentNullException" />
		internal Mock(object proxy)
		{
			if (proxy == null)
				throw new ArgumentNullException("proxy");

			_proxy = proxy;
		}

		/// <summary>
		/// An object of type <typeparamref name="T" /> to be used whenever the Mocked object is needed.
		/// </summary>
		/// <remarks>
		/// This property provides access to the proxy that is mocking the actual type.  Use this property
		/// when a reference to an object that is of the mocked type is needed.
		/// </remarks>
		/// <example>
		/// This example shows how a presenter needs arguments of the right type during construction.  The
		/// <c>MockObject</c> property is used because the presenter can't be instantiated with a <see cref="Mock{T}"/>
		/// argument.
		/// <code>
		/// interface IView { ... }
		/// interface IModel { ... }
		/// 
		/// Mock&lt;IView&gt; view = factory.CreateMock&lt;IView&gt;();
		/// Mock&lt;IModel&gt; view = factory.CreateMock&lt;IModel&gt;();
		/// 
		/// Presenter p = new Presenter(view.MockObject, model.MockObject);
		/// </code>
		/// </example>
		public T MockObject
		{
			get
			{
				return (T)_proxy;
			}
		}

		/// <summary>
		/// A syntax property used to access members that specify the number of times an expectation could occur.
		/// </summary>
		/// <remarks>This would be considered to be the main property of a <see cref="Mock{T}"/> class.  This property
		/// provides access to all other expectation setup methods.</remarks>
		/// <example>
		/// In this example:
		/// <list type="bullet">
		/// <item>
		/// <term><c>mock</c></term>
		/// <description>is an instance of a <see cref="Mock{T}"/>.</description>
		/// </item>
		/// <item>
		/// <term><c>Expects</c></term>
		/// <description>refers to this property.</description>
		/// </item>
		/// <item>
		/// <term><c>One</c></term>
		/// <description>is the number of times this action is expected.</description>
		/// </item>
		/// <item>
		///	<term><c>SetPropertyTo</c></term>
		/// <description>is the type of expection.  <see cref="IMethodSyntax{TType}.SetPropertyTo"/> means the mock expects a property to be set to a value.</description>
		/// </item>
		/// <item>
		/// <term><c>m</c></term>
		/// <description>is the variable in our anonymous method that represents the mocked interface or class.</description>
		/// </item>
		/// <item>
		///	<term><c>Prop</c></term>
		/// <description>is the property on the mocked interface or class that the expectation is for.  It will be set to the value 3.</description>
		/// </item>
		/// </list>
		/// <code>
		/// 
		/// //create an expectation that the property Prop will be set to 3
		/// mock.Expects.One.SetPropertyTo(m=>m.Prop=3);
		/// 
		/// </code>
		/// View more of this code in the <see cref="Mock{T}"/> example.
		/// </example>
		/// <seealso cref="NMock2.Expects{T}"/>
		public Expects<T> Expects
		{
			get
			{
				return new Expects<T>(_proxy);
			}
		}

		/// <summary>
		/// Returns a <see cref="Mock{T}" /> instance of the type being specified in <typeparamref name="TType"/>.
		/// </summary>
		/// <typeparam name="TType"></typeparam>
		/// <returns></returns>
		/// <remarks>This method will not throw an exception if the cast is not successful.  It will return null.  Use <c cref="Mock{T}.As{TType}(bool)"/> to specify if an exception should be thrown.</remarks>
		public Mock<TType> As<TType>() where TType : class
		{
			return As<TType>(false);
		}

		/// <summary>
		/// Returns a <see cref="Mock{T}" /> instance of the type being specified in <typeparamref name="TType"/>.
		/// </summary>
		/// <typeparam name="TType"></typeparam>
		/// <param name="throwException"></param>
		/// <returns></returns>
		/// <exception cref="InvalidCastException"></exception>
		/// <remarks>Use this method to specify if an exception should be thrown when a cast is not valid.</remarks>
		public Mock<TType> As<TType>(bool throwException) where TType : class
		{
			TType mock = _proxy as TType;
			if(mock != null)
				return new Mock<TType>(_proxy);
			else
			{
				if (throwException)
					throw new InvalidCastException(string.Format("The mock does not implement {0}.  Use the CreateMock method that accepts a list of Types to mock more than the default type.", typeof (TType).Name));
				else
					return null;
			}
		}
	}
}